package edu.polsl.bio.str.manager.command;

import edu.polsl.bio.str.common.constant.JobStatus;
import edu.polsl.bio.str.common.entity.*;
import edu.polsl.bio.str.common.vo.JobData;
import edu.polsl.bio.str.manager.dao.*;
import edu.polsl.bio.str.manager.event.NewJobGroupEvent;
import edu.polsl.bio.str.manager.messaging.ManagerMessagePublisher;
import edu.polsl.bio.str.manager.vo.DotPlotRequest;
import edu.polsl.bio.tools.str.StrMatcherConfig;
import edu.polsl.bio.tools.str.StrRecord;
import edu.polsl.bio.tools.util.FastaReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * //todo class description
 * <p/>
 * Created on: Jul 22, 2011 2:52:37 PM <br/>
 * <a href="http://www.polsl.pl">www.polsl.pl</a>
 *
 * @author lukasz.olczak@polsl.pl
 */
public class JobLoaderCommandImpl extends BaseCommand<DotPlotRequest> implements JobLoaderCommand {
  /**
   * slf4j Logger.
   */
  private static final Logger log = LoggerFactory.getLogger(JobLoaderCommandImpl.class.getName());

  private StrDao strDao;

  private IndividualDao individualDao;

  private ChromosomeDao chromosomeDao;

  private JobGroupDao jobGroupDao;

  private JobDao jobDao;

  private JobDataDao jobDataDao;

  private int maxChunk;

  private int lefFlankSize;

  private int rightFlankSize;

  private StrMatcherConfig strMatcherConfig;

  private ManagerMessagePublisher managerEventPublisher;

  private List<Str> refStrs;

  private List<Str> queryStrs;

  private String refFasta;

  private String queryFasta;

  private List<JobData> jobDataList;

  private List<Job> jobs;

  private JobLoaderCommand proxy;

  @Override
  protected void performLogic(DotPlotRequest context) {
    try {
      proxy.getStrs(context);
      proxy.createJobData();
      proxy.saveJobData();
      long jobGroupId = proxy.persistJobs(context);
      managerEventPublisher.publishEvent(new NewJobGroupEvent(jobGroupId));
    } catch (Exception e) {
      log.error("Exception occurred during creation of dot plot job", e);
    }
  }

  @Override
  @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
  public void getStrs(DotPlotRequest dotPlotRequest) throws Exception {
    log.info("Starting job group for refId={}, refChrom={}, queryId={}, queryChromId={}", new Object[]
        {dotPlotRequest.getRefIndividualId(), dotPlotRequest.getRefChromosomeId(),
         dotPlotRequest.getQueryIndividualId(), dotPlotRequest.getQueryChromosomeId()});
    refStrs = strDao.findAll(dotPlotRequest.getRefIndividualId(), dotPlotRequest.getRefChromosomeId());
    queryStrs = strDao.findAll(dotPlotRequest.getQueryIndividualId(), dotPlotRequest.getQueryChromosomeId());
    log.debug("Found {} ref strs, {} query strs", refStrs.size(), queryStrs.size());
    Individual refIndividual = individualDao.getById(dotPlotRequest.getRefIndividualId());
    Individual queryIndividual = individualDao.getById(dotPlotRequest.getQueryIndividualId());

    Chromosome refChromosome = chromosomeDao.getById(dotPlotRequest.getRefChromosomeId());
    Chromosome queryChromosome = chromosomeDao.getById(dotPlotRequest.getQueryChromosomeId());

    refFasta = refIndividual.getPathBase() + refChromosome.getName() + refIndividual.getExt();
    queryFasta = queryIndividual.getPathBase() + queryChromosome.getName() + queryIndividual.getExt();
  }

  @Override
  public void createJobData() throws Exception {
    log.debug("Loading {}", refFasta);
    FastaReader refReader = new FastaReader(refFasta);

    log.debug("Loading {}", queryFasta);
    FastaReader queryReader = new FastaReader(queryFasta);

    List<StrRecord> refRecords = newStrRecords(refStrs, refReader);
    List<StrRecord> queryRecords = newStrRecords(queryStrs, queryReader);

    jobDataList = split(refRecords, queryRecords);
  }


  @Override
  public void saveJobData() throws Exception {
    log.debug("Persisting {} jobs data", jobDataList.size());
    jobs = new LinkedList();
    for (JobData jobData : jobDataList) {
      String filePath = jobDataDao.saveJobData(jobData);
      Job job = new Job();
      job.setStatus(JobStatus.WAITING);
      job.setRefStart(jobData.getRefStrs().get(0).getStartIndex());
      job.setRefEnd(jobData.getRefStrs().get(jobData.getRefStrs().size() - 1).getStartIndex());
      job.setQueryStart(jobData.getQueryStrs().get(0).getStartIndex());
      job.setQueryEnd(jobData.getQueryStrs().get(jobData.getQueryStrs().size() - 1).getStartIndex());
      job.setDataFilePath(filePath);
      jobs.add(job);
    }
  }

  @Override
  @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
  public long persistJobs(DotPlotRequest dotPlotRequest) throws Exception {
    JobGroup jobGroup = new JobGroup();
    jobGroup.setRefId(dotPlotRequest.getRefIndividualId());
    jobGroup.setQueryId(dotPlotRequest.getQueryIndividualId());
    jobGroup.setRefChromosomeId(dotPlotRequest.getRefChromosomeId());
    jobGroup.setQueryChromosomeId(dotPlotRequest.getQueryChromosomeId());
    jobGroup.setStartDate(new Date());
    jobGroup.setFinishedJobs(0);
    jobGroup.setOverallJobs(jobs.size());
    jobGroup.setEndDate(null);
    jobGroup.setInvokedBy("user");

    jobGroup = jobGroupDao.create(jobGroup);
    log.debug("Persisting {} jobs", jobs.size());
    for (Job job : jobs) {
      job.setJobGroupId(jobGroup.getId());
      job = jobDao.create(job);
    }
    return jobGroup.getId();
  }

  private List<JobData> split(List<StrRecord> refRecords, List<StrRecord> queryRecords) {
    List<List<StrRecord>> refChunks = splitRecords(refRecords, maxChunk);
    List<List<StrRecord>> queryChunks = splitRecords(queryRecords, maxChunk);

    List<JobData> jobDataList = new LinkedList();

    for (List<StrRecord> refChunk : refChunks) {
      for (List<StrRecord> queryChunk : queryChunks) {
        JobData jobData = new JobData();
        jobData.setRefStrs(refChunk);
        jobData.setQueryStrs(queryChunk);
        jobData.setConfig(strMatcherConfig);
        jobDataList.add(jobData);
      }
    }

    return jobDataList;
  }

  private List<List<StrRecord>> splitRecords(List<StrRecord> records, int maxChunk) {
    List<List<StrRecord>> chunks = new LinkedList();
    for (int i = 0; i < records.size();) {
      int fromIndex = i;
      int toIndex = i + maxChunk;
      if (toIndex > records.size()) {
        toIndex = records.size();
      }
      chunks.add(new ArrayList(records.subList(fromIndex, toIndex)));
      i += maxChunk;
    }

    return chunks;
  }

  private List<StrRecord> newStrRecords(List<Str> strs, FastaReader reader) {
    List<StrRecord> records = new ArrayList(strs.size());
    for (Str str : strs) {
      StrRecord strRecord = newStrRecord(str, reader);
      if (strRecord != null)
        records.add(strRecord);
    }
    return records;
  }

  private StrRecord newStrRecord(Str str, FastaReader reader) {
    StrRecord strRecord = new StrRecord(str.getMotif(), str.getStart(), str.getEnd(), str.getRatio());

    int ls = strRecord.getStartIndex() - lefFlankSize;
    int le = strRecord.getStartIndex() - 1;
    int rs = strRecord.getEndIndex() + 1;
    int re = strRecord.getEndIndex() + rightFlankSize;

    if (ls < 1 || re > reader.getFastaLength()) {
      return null;
    }

    strRecord.setLeftFlank(reader.subSeq(ls, le));
    strRecord.setRightFlank(reader.subSeq(rs, re));

    return strRecord;
  }

  public void setStrDao(StrDao strDao) {
    this.strDao = strDao;
  }

  public void setIndividualDao(IndividualDao individualDao) {
    this.individualDao = individualDao;
  }

  public void setChromosomeDao(ChromosomeDao chromosomeDao) {
    this.chromosomeDao = chromosomeDao;
  }

  public void setMaxChunk(int maxChunk) {
    this.maxChunk = maxChunk;
  }

  public void setLefFlankSize(int lefFlankSize) {
    this.lefFlankSize = lefFlankSize;
  }

  public void setRightFlankSize(int rightFlankSize) {
    this.rightFlankSize = rightFlankSize;
  }

  public void setStrMatcherConfig(StrMatcherConfig strMatcherConfig) {
    this.strMatcherConfig = strMatcherConfig;
  }

  public void setJobGroupDao(JobGroupDao jobGroupDao) {
    this.jobGroupDao = jobGroupDao;
  }

  public void setJobDao(JobDao jobDao) {
    this.jobDao = jobDao;
  }

  public void setJobDataDao(JobDataDao jobDataDao) {
    this.jobDataDao = jobDataDao;
  }

  public void setManagerEventPublisher(ManagerMessagePublisher managerEventPublisher) {
    this.managerEventPublisher = managerEventPublisher;
  }

  @Override
  public void setProxy(JobLoaderCommand proxy) {
    this.proxy = proxy;
  }
}
